using System;
using System.Drawing;
using System.ComponentModel;
using System.Collections;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;

using DirectShowLib;


namespace VideoCapture
{
    public class VideoCapture
    {
        private IMediaControl m_MediaControl = null;
        private IGraphBuilder m_GraphBuilder = null;
        private IBaseFilter m_TheCompressor = null;
        private IBaseFilter m_VideoDevice = null;
        private IBaseFilter m_AudioDevice = null;
        
        /// <summary>
        /// Initialize the capture Graphic with the preview controler
        /// </summary>
        /// <param name="videoPathName">The path to save the video</param>
        /// <param name="hControl">The Control to be display while capturing</param>
        public void InitialVideo(string videoPathName, Control panl1)
        {
            //Create the device filter
            m_VideoDevice = CreateFilter(FilterCategory.VideoInputDevice, "Decklink Video Capture");
            m_AudioDevice = CreateFilter(FilterCategory.AudioInputDevice, "Decklink Audio Capture");

            //Create the Graph
            m_GraphBuilder = (IGraphBuilder)new FilterGraph();

            //Create the Capture Graph Builder
            ICaptureGraphBuilder2 captureGraphBuilder = null;
            captureGraphBuilder = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();

            //Create the media control for controlling the graph
            m_MediaControl = (IMediaControl)this.m_GraphBuilder;

            // Attach the filter graph to the capture graph
            int hr = captureGraphBuilder.SetFiltergraph(this.m_GraphBuilder);
            DsError.ThrowExceptionForHR(hr);

            //Add the Video input device to the graph
            hr = m_GraphBuilder.AddFilter(m_VideoDevice, "Video Capture Device");
            DsError.ThrowExceptionForHR(hr);

            //Add the Audio Input device to the graph
            hr = m_GraphBuilder.AddFilter(m_AudioDevice, "Audio Capture Device");
            DsError.ThrowExceptionForHR(hr);

            //Create the file writer part of the graph. SetOutputFileName does this for us, and returns the mux and sink
            IBaseFilter mux;
            IFileSinkFilter sink;
            hr = captureGraphBuilder.SetOutputFileName(MediaSubType.Avi, videoPathName, out mux, out sink);
            DsError.ThrowExceptionForHR(hr);

            //Render any preview pin of the device
            hr = captureGraphBuilder.RenderStream(PinCategory.Preview, MediaType.Video, m_VideoDevice, null, null);
            DsError.ThrowExceptionForHR(hr);


            //Connect the device and compressor to the mux to render the capture part of the graph
            hr = captureGraphBuilder.RenderStream(PinCategory.Capture, MediaType.Video, m_VideoDevice, m_TheCompressor, mux);
            DsError.ThrowExceptionForHR(hr);

            hr = captureGraphBuilder.RenderStream(PinCategory.Capture, MediaType.Audio, m_AudioDevice, null, mux);
            DsError.ThrowExceptionForHR(hr);

           




            //IVideoWindow ivw = (IVideoWindow)m_GraphBuilder;
            ConfigVideoWindow(panl1);

            Marshal.ReleaseComObject(mux);
            Marshal.ReleaseComObject(sink);
            Marshal.ReleaseComObject(captureGraphBuilder);
        }

        public void Capture()
        {
            m_MediaControl.Run();
        }

        public void Stop()
        {
            try
            {
                m_MediaControl.Stop();
            }
            catch (Exception err)
            { }
            finally
            {
                Marshal.ReleaseComObject(m_MediaControl);
                Marshal.ReleaseComObject(m_GraphBuilder);
            }
        }

        private void ConfigVideoWindow(Control hControl)
        {
            int hr;

            IVideoWindow ivw = (IVideoWindow)m_GraphBuilder;

            // Set the parent
            hr = ivw.put_Owner(hControl.Handle);
            DsError.ThrowExceptionForHR(hr);

            // Turn off captions, etc
            hr = ivw.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings);
            DsError.ThrowExceptionForHR(hr);

            // Yes, make it visible
            hr = ivw.put_Visible(OABool.True);
            DsError.ThrowExceptionForHR(hr);

            // Move to upper left corner
            Rectangle rc = hControl.ClientRectangle;
            hr = ivw.SetWindowPosition(0, 0, rc.Right, rc.Bottom);
            DsError.ThrowExceptionForHR(hr);
        }

        //Find the Device and return the Base Filter
        private IBaseFilter CreateFilter(Guid category, string friendlyname)
        {
            object source = null;
            Guid iid = typeof(IBaseFilter).GUID;
            foreach (DsDevice device in DsDevice.GetDevicesOfCat(category))
            {
                if (device.Name.CompareTo(friendlyname) == 0)
                {
                    device.Mon.BindToObject(null, null, ref iid, out source);
                    break;
                }
            }
            return (IBaseFilter)source;
        }

        //Return the string represent the Really Time and the BCD for deck's location 
        public string TimeBarDisplay(IAMTimecodeReader timeReader, out int BCD)
        {
            string timerProgress = null;
            BCD = 0;

            if (timeReader != null)
            {
                int frame, hour, min, sec;
                string sFrame, sHour, sMin, sSec;

                TimeCodeSample sample;
                sample.timecode.dwFrames = 0;

                sample.dwFlags = (int)ExtDeviceCaps.TimeCodeRead;

                timeReader.GetTimecode(out sample);

                BCD = sample.timecode.dwFrames;

                BCDToFrameCount(out frame, out hour, out min, out sec, BCD);

                if (frame > 30)
                {
                    frame -= 40;
                    BCD -= 64;
                }
                if (frame < 10)
                    sFrame = "0" + frame;
                else sFrame = frame.ToString();
                if (hour < 10)
                    sHour = "0" + hour;
                else sHour = hour.ToString();
                if (min < 10)
                    sMin = "0" + min;
                else sMin = min.ToString();

                if (sec < 10)
                    sSec = "0" + sec;
                else sSec = sec.ToString();

                timerProgress = "Time : " + sHour + ":" + sMin + ":" + sSec + "," + sFrame;
            }
            return timerProgress;
        }

        public void BCDToFrameCount(out int frame, out int hour, out int min, out int sec, int BCD)
        {
            frame = BCDToDecimal(BCD & 0x000000FF);
            sec = BCDToDecimal((BCD & 0x0000FF00) >> 8);
            min = BCDToDecimal((BCD & 0x00FF0000) >> 16);
            hour = BCDToDecimal((int)(BCD & 0xFF000000) >> 24);
        }
        private int BCDToDecimal(int BCD)
        {
            return (10 * ((BCD & 0xF0) >> 4) + (BCD & 0x0F));
        }

        public int FrameCountToBCD(int hour, int min, int sec, int frame)
        {
            int BCDTimecode;

            return BCDTimecode = (int)(((DecimalToBCD(hour) << 24) & 0xFF000000) | ((DecimalToBCD(min) << 16) & 0x00FF0000) | ((DecimalToBCD(sec) << 8) & 0x0000FF00) | (DecimalToBCD(frame) & 0x000000FF));
        }

        private int DecimalToBCD(int Decimal)
        { 
            return (Decimal % 10) | ((Decimal / 10 % 10) << 4);
        }



    }
}
